package tfts

import (
	"fmt"
	"github.com/expr-lang/expr"
	"math"
	"math/rand"
	"strconv"
	"strings"
)

const (
	tick = 1000
)

const (
	TrueDamage = 1.8
)

const (
	postMana   = 0.03
	prevMana   = 0.01
	beatenMana = 8.0
)

var EndTime = 20
var previewRun = ""
var bundles = make(map[string]*Bundle)
var beatenManaEnable = true

type Fight struct {
	*Form
	global *championMeta // 整局战斗都生效的属性
	record0
	castRecord
	multicast int  // 多重施法，会使施法前摇增加响应倍数
	castRate  int  // 施法倍率
	ticks     int  // 当前时间，单位ms
	casting   bool // 是否施法中
	wound     bool // 是否被减疗
	shred     bool // 是否被减抗
	locks     []validI
	buffs     map[string]*buff
	attrs     []attributeI
	handlers  []handlerI
	states    []StatusI
	prevCall  []func(*Fight) // 开局调用
	postCall  []func(*Fight) // 结束调用
	EndTime   int
	env       map[string]any // 表达式替换所用变量
}

// todo删除bundle,mete已内嵌skill
type Bundle struct {
	*championMeta
	*skill
}

func Hero(name string) (res *Fight) {
	key := strings.ToLower(name)
	if b, ok := bundles[key]; ok {
		res = b.Build()
	} else if cfg, ok := configs[key]; ok {
		res = cfg.RegisterBundle().Build()
	} else {
		panic(fmt.Sprintf("unknown champion: %s", name))
	}
	res.env = make(map[string]any)
	res.name = name
	previewRun = name
	return res
}

func Reload() *Fight {
	if previewRun != "" {
		return Hero(previewRun)
	}
	panic("no champion selected")
}

func AddBuff(name string, duration int, event Event, attrs ...*attrMeta) func(*Fight) {
	return func(fight *Fight) {
		b := NewBuff(event, duration, attrs...)
		b.attach(fight)
		TraceLog("%4.1f秒:[%s]添加Buff %s\n", fight.Current(), name, b.String())
	}
}

func (b *Bundle) Build() *Fight {
	f := &Fight{
		buffs: make(map[string]*buff),
	}
	f.EndTime += EndTime
	// 复制championMeta以避免修改
	cp := *b.championMeta
	// 创建一个空Form来保存延迟初始化的meta
	f.Form = &Form{id: "0"}
	f.championMeta = &cp
	// 直接的数值修改使用holder以避免修改championMeta
	f.attrs = append(f.attrs, baseModifier())
	f.skill = b.skill
	f.multicast = 1
	f.castRate = 100
	return f
}

func (f *Fight) Start(input ...string) record0 {
	if len(input) == 0 {
		if len(f.name) == 0 {
			f.name = "-"
		}
		input = append(input, f.name)
	}
	for k, v := range exprEnv {
		if _, ok := f.env[k]; !ok {
			f.env[k] = v
		}
	}
	if outputLevel&Local == Local {
		openFd(input[0])
	}
	defer closeFdIfNeed()
	end := f.EndTime * tick
	atkTick := -1                // 攻击前摇
	castTick := 0                // 施法前摇
	mitigate := 0.0              // 总伤害缓和
	beatenTick, lockTick := 0, 0 // 挨打回蓝tick，挨打不会回蓝tick
	regenTick := -1              // 每半秒回蓝tick，回蓝次数
	// 延迟初始化
	f.attach(f)

	for _, prev := range f.prevCall {
		prev(f)
	}

	// 使ticks从0开始计算
	f.ticks = -1

	for f.ticks < end {
		f.ticks++

		if f.casting || f.lockMana() {
			lockTick++
			if regenTick > 0 {
				regenTick += f.regenAllowed()
			}
		} else {
			beatenTick++
			regenTick += f.regenAllowed()
		}

		// 每0.5秒计算一次回蓝
		if regenTick >= tick/2 {
			// 每半秒回一次蓝，所以除以2
			regen := f.regen() * f.manaAmp() / 2
			f.mana += regen
			f.TotalManaReg += regen
			regenTick = 0
			f.RegenTimes++
			if regen > 0 {
				TraceLog("%4.1f秒:第%d次自动蓝量回复, 回蓝%.1f\n", f.Current(), f.RegenTimes, regen)
			}
		}

		if f.ticks >= tick && f.ticks%tick == 0 {
			f.publish(TimeGoE)
			f.showStatus()
			if f.isFighter() {
				mitigate += float64(100+f.ar()) / 100 / f.taken()
			}
		}

		if castTick > 0 {
			castTick--
		}

		if !f.casting {
			atkTick += f.attackAllowed()
		}

		if f.as()*f.speed*float64(atkTick)/float64(tick) >= 1.000 {
			f.attacks()
			if rand.Float64() <= f.strike() {
				TraceLog("%4.1f秒:触发双重打击\n", f.Current())
				f.attacks()
				tmp := tick
				// 双重打击即以7.0攻速A一下
				f.ticks += int(1.0 / 7.0 * float64(tmp))
			}
			atkTick = 0
		}

		if f.canCast() && f.skill != nil && f.charge() {
			f.casting = true
			//f.SpendMana += f.castMana()
			castTick = f.multicast * f.skill.swing * tick / 10 // 前摇的单位是100ms
			actionLog("%4.1f秒:第%d次施法读条, 施法时间%.1f秒, 当前蓝量%.1f\n", f.Current(), f.CastTimes+1, float64(f.skill.swing)/10, f.mana)
		}

		if f.casting && castTick == 0 {
			for i := 0; i < f.multicast; i++ {
				if f.skillCrit() && rand.Float64() < f.critRate() {
					f.publish(SkillCritE)
				}
				f.publish(BeforeCastE)
				f.skill.cast(f)
				f.publish(AfterCastE)
			}
			f.casting = false
		}
	}

	TraceLog("\n")

	callRecord := make([]string, 0)
	for _, foo := range f.handlers {
		a, ok := foo.(*action)
		if !ok {
			continue
		}
		callRecord = append(callRecord, fmt.Sprintf("%s[%d]", a.name, a.count))
	}

	if len(callRecord) > 0 {
		actionLog("被动触发: %s\n", strings.Join(callRecord, ", "))
	}

	f.TotalDmg = f.AllAtkDmg() + f.AllCastDmg() + f.AllPassiveDmg()
	dps := f.TotalDmg / f.EndTime
	f.DPS = dps
	tpps := 0
	if f.isFighter() {
		f.PureTaken = int(f.hp() * f.hpAmp() * mitigate / math.Floor(f.Current()))
		f.SelfMitigate = f.Sustain + f.PureTaken
		tps := f.SelfMitigate / f.EndTime
		tpps = tps
	}

	percent := float64(f.AllCastDmg()*100) / float64(f.TotalDmg)
	if !deferredRun {
		metricLog("%-5s(%.0f秒) dps=%d, castTime=%d, atkTime=%d, castAvg=%d, atkAvg=%d, skillPct=%.1f%%\n", input[0], f.Current(), dps, f.CastTimes, f.AtkTimes, f.AvgCastDmg(), f.AvgAtkDmg(), percent)
		if f.isFighter() {
			metricLog("%-5s(%.0f秒) tps=%d, selfMitigate=%d, healthTaken=%d\n", input[0], f.Current(), tpps, f.SelfMitigate, f.PureTaken)
		}
	} else {
		out := fmt.Sprintf("%-5s(%.0f秒) dps=%d, castTime=%d, atkTime=%d, castAvg=%d, atkAvg=%d, skillPct=%.1f%%\n", input[0], f.Current(), dps, f.CastTimes, f.AtkTimes, f.AvgCastDmg(), f.AvgAtkDmg(), percent)
		if tpps != 0 {
			out = fmt.Sprintf("%-5s(%.0f秒) dps=%d, tps=%d, castTime=%d, atkTime=%d, castAvg=%d, atkAvg=%d, skillPct=%.1f%%\n", input[0], f.Current(), dps, tpps, f.CastTimes, f.AtkTimes, f.AvgCastDmg(), f.AvgAtkDmg(), percent)
		}
		results = append(results, result0{dps, out})
	}
	return f.record0
}

func (f *Fight) PrevCall(call func(fight *Fight)) *Fight {
	f.prevCall = append(f.prevCall, call)
	return f
}

func (f *Fight) PostCall(call func(fight *Fight)) *Fight {
	f.postCall = append(f.postCall, call)
	return f
}

func (f *Fight) beaten() {
	if !f.isFighter() || !beatenManaEnable || f.ManaLess {
		return
	}
	NewAction(f).Repeat(1).On(TimeGoE).AddCall("挨打回蓝", func(f *Fight) {
		if !f.lockMana() {
			f.mana += beatenMana * f.manaAmp()
		}
	})
}

func (f *Fight) Current() float64 {
	return float64(f.ticks) / float64(tick)
}

func (f *Fight) attacks() float64 {
	dmg := f.attack * f.ad() * (1 + f.critRate()*f.critAmp()) * f.atkAmp()
	ae := 0.0
	if f.dmgType == Magic {
		ae += f.ae() * f.AP()
	} else {
		ae += f.ae() * f.ad()
	}
	if f.skillCrit() {
		ae *= 1 + f.critRate()*f.critAmp()
	}
	dmg += ae
	dmg *= f.amp()
	dmg *= 1 + f.uAmp()
	f.AtkTimes++
	f.record(dmg, AttackS)
	f.attacks0++
	if !f.lockMana() {
		mana := float64(f.hitMana()) * f.manaAmp()
		f.mana += mana
		actionLog("%4.1f秒:第%d次攻击, 伤害%.0f, 回复法力%.1f, 当前法力值%.1f\n", f.Current(), f.AtkTimes, dmg, mana, f.mana)
	} else {
		actionLog("%4.1f秒:第%d次攻击, 伤害%.0f, 法力锁定中\n", f.Current(), f.AtkTimes, dmg)
	}
	f.recordSteal(dmg, AttackS)
	if rand.Float64() < f.critRate() {
		f.publish(CriticalE)
		f.critical++
	}
	f.publish(AttackE)
	return dmg
}

func (f *Fight) charge() bool {
	actual := f.castMana()
	if f.mana >= actual {
		TraceLog("%4.1f秒:施法蓝量%.1f, 当前蓝量%.1f\n", f.Current(), actual, f.mana)
		f.mana -= actual
		return true
	}
	return false
}

func (f *Fight) castMana() float64 {
	return float64(f.skill.mana)*f.manaShrink() - float64(f.manaDec())
}

func (f *Fight) recordSteal(dmg float64, source Source) bool {
	if !f.isFighter() {
		return false
	}
	dmg = math.Floor(dmg)
	omnivamp := f.steal()
	// 默认被减疗
	amp := f.hlAmp() * f.rcvAmp() * 0.67
	// todo
	//mitigate := float64(100+f.ar()) / 100 / f.taken()
	tmp1 := f.ar()
	tmp2 := f.taken()
	mitigate := float64(100+tmp1) / 100 / tmp2
	heal := dmg * omnivamp * amp
	if source == AttackS {
		heal += f.hit() * amp
	}
	selfMitigate := heal * mitigate
	f.Sustain += int(selfMitigate)
	f.BeatenMana += heal * (mitigate*prevMana + postMana)
	switch source {
	case AttackS:
		actionLog("%4.1f秒:第%d次攻击伤害%.0f, 回复生命值%.0f, 缓和系数%.2f, 治疗系数%.2f\n", f.Current(), f.AtkTimes, dmg, heal, mitigate, amp)
	case CastS:
		actionLog("%4.1f秒:第%d次施法伤害%.0f, 回复生命值%.0f, 缓和系数%.2f, 治疗系数%.2f\n", f.Current(), f.CastTimes, dmg, heal, mitigate, amp)
	default:
		panic(fmt.Sprintf("unknown source: %d", source))
	}
	return true
}

func (f *Fight) Adds(src ...*attrMeta) *Fight {
	for _, attr := range src {
		f.Add(attr)
	}
	return f
}

func (f *Fight) Add(src *attrMeta) *attrMeta {
	for _, attr := range f.attrs {
		if match, ok := attr.(*modifier); ok {
			match.Add(src)
			return &match.attrMeta
		}
	}
	panic("can't find holder")
}

func (f *Fight) recover0(c *callable) {
	mitigate := float64(100+f.ar()) / 100 / f.taken()

	if c.Grow != "" {
		grow := f.parseRecover(c.Grow)
		grow = math.Floor(grow)
		amp := f.hpAmp()
		actionLog("%4.1f秒:[%s]触发, 生命值增长%.0f, 缓和系数%.2f, 生命值系数%.2f\n", f.Current(), c.name, grow, mitigate, amp)
		f.Add(HP(int(grow)))
	}

	if c.Heal != "" {
		heal := f.parseRecover(c.Heal)
		heal = math.Floor(heal)
		// 默认被减疗
		amp := f.hlAmp() * f.rcvAmp() * 0.67
		actionLog("%4.1f秒:[%s]触发, 治疗生命值%.0f, 缓和系数%.2f, 治疗系数%.2f\n", f.Current(), c.name, heal, mitigate, amp)
		f.Sustain += int(heal * amp * mitigate)
		f.BeatenMana += heal * amp * (mitigate*prevMana + postMana)
		f.publish(HealedE)
	}

	if c.Shield != "" {
		shield := f.parseRecover(c.Shield)
		shield = math.Floor(shield)
		amp := f.shdAmp() * f.rcvAmp()
		actionLog("%4.1f秒:[%s]触发, 生成护盾%.0f, 缓和系数%.2f, 护盾系数%.2f\n", f.Current(), c.name, shield, mitigate, amp)
		f.Sustain += int(shield * amp * mitigate)
		f.publish(ShieldE)
	}
}

func (f *Fight) parseRecover(rcv string) float64 {
	f.updateEnv()
	program, err := expr.Compile(rcv, expr.Env(f.env))
	if err != nil {
		panic(fmt.Sprintf(`recvoer "%s" parse error: %v`, rcv, err))
	}
	output, err := expr.Run(program, f.env)
	if err != nil {
		panic(fmt.Sprintf(`recover "%s" eval error: %v`, rcv, err))
	}
	switch t := output.(type) {
	case int:
		return float64(t)
	case float64:
		return t
	default:
		panic(fmt.Sprintf(`recover "%s" eval error: %v`, rcv, err))
	}
}

func (f *Fight) parseDamage(dmg string) float64 {
	if len(dmg) == 0 || strings.ReplaceAll(dmg, " ", "") == "" {
		return 0
	}
	if res, err := strconv.ParseFloat(dmg, 64); err == nil {
		return res
	}
	f.updateEnv()
	program, err := expr.Compile(dmg, expr.Env(f.env))
	if err != nil {
		panic(fmt.Sprintf(`damage "%s" parse error: %v`, dmg, err))
	}
	output, err := expr.Run(program, f.env)
	if err != nil {
		panic(fmt.Sprintf(`damage "%s" eval error: %v`, dmg, err))
	}
	switch t := output.(type) {
	case int:
		return float64(t)
	case float64:
		return t
	default:
		panic(fmt.Sprintf(`damage "%s" eval error: %v`, dmg, err))
	}
}

func (f *Fight) showStatus() {
	TraceLog("%4.1f秒:Ad=%.0f, Ap=%.0f, Amp=%.0f, AtkAmp=%.0f, AE=%.0f, Critable=%v\n", f.Current(), f.ad()*100, f.AP()*100, f.amp()*100, f.atkAmp()*100, f.ae(), f.skillCrit())
	TraceLog("%4.1f秒:BonusSpeed=%d, Speed=%.2f, CC=%.0f, CritDamage=%.0f, UAmp=%.0f\n", f.Current(), f.BonusAs()-100, f.as()*f.speed, f.critRate()*100, f.critAmp()*100, f.uAmp()*100)
	if f.isFighter() {
		TraceLog("%4.1f秒:Health=%.0f, Armor=%d, Durability=%.0f\n", f.Current(), f.hp()*f.hpAmp(), f.ar(), 100-f.taken()*100)
		TraceLog("%4.1f秒:Omnivamp=%.0f, HealAmp=%.0f, RecoverAmp=%.0f\n", f.Current(), f.steal()*100, f.hlAmp()*100, f.rcvAmp()*100)
	}
}

func (f *Fight) lockMana() bool {
	for _, foo := range f.locks {
		if foo.valid() {
			return true
		}
	}
	return false
}

func (f *Fight) publish(flag Event) {
	for _, handler := range f.handlers {
		handler.handle(flag)
	}
}

func (f *Fight) updateEnv() {
	f.env["remain"] = f.EndTime - int(f.Current())
	f.env["end"] = f.EndTime
	f.env["time"] = f.Current()
	f.env["atk"] = f.AtkTimes
	f.env["cast"] = f.CastTimes
	f.env["hp"] = f.hp() * f.hpAmp() / 100
	f.env["as"] = f.as() * 100
	f.env["ap"] = f.AP()
	f.env["ad"] = f.attack
	f.env["min"] = min
	f.env["tdmg"] = TrueDamage
	f.env["atk0"] = f.attacks0
	f.env["crit"] = f.critical
}

func (f *Fight) Var(name string, value any) {
	f.env[name] = value
}

func (f *Fight) SetRole(role Role) {
	f.Role = role
}
